package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;

import static com.hmdp.utils.RedisConstants.*;
@Component
public class CacheRedis {
    private final StringRedisTemplate stringRedisTemplate;
    private final static ExecutorService executor = new ThreadPoolExecutor(10, 10, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10));
    public CacheRedis(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }
    //任意对象存储在string类型key，设置过期时间
    public void set(String key, Object value, Long ttl, TimeUnit timeUnit){
        String jsonValue = JSONUtil.toJsonStr(value);
        stringRedisTemplate.opsForValue().set(key,jsonValue,ttl,timeUnit);
    }
    //任意对象存储在string类型key，设置逻辑过期时间
    public void setWithLogicExpire(String key, Object value, Long time, TimeUnit timeUnit){
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        redisData.setData(value);
        String jsonValue = JSONUtil.toJsonStr(redisData);
        stringRedisTemplate.opsForValue().set(key,jsonValue);
    }

    public <R,ID> R queryWithPassThrough(String prefixKey, Class<R> type, ID id,
                                           Function<ID,R> dbFallback, Long ttl, TimeUnit timeUnit) {
        //1.从redis中查询商户
        String key = prefixKey + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.1命中，如果不是空值直接返回
        if(json != null && !json.equals("")){
            return JSONUtil.toBean(json,type);
        }
        //2.2命中，是空值返回null。
        if(json != null){
            return null;
        }
        //3.未命中，从数据库中查询
        R bean = dbFallback.apply(id);
        //4.不存在,返回404错误信息
        if(bean == null) {
            //缓存空值，避免缓存穿透
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return null;
        }
        //5.存在，写回redis中
        String jsonStr = JSONUtil.toJsonStr(bean);
        stringRedisTemplate.opsForValue().set(key,jsonStr,ttl, timeUnit);
        //6.返回信息
        return bean;
    }

    public <R, ID> R queryWithLogicExpire(String prefixKey,String prefixLockKey, Class<R> type,ID id,
                                          Function<ID,R> dbFallback,Long time, TimeUnit timeUnit) {
        //1.从redis中查询商户
        String key = prefixKey + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.未命中，返回错误信息
        if(StrUtil.isBlank(json)) {
            return null;
        }
        //3.命中，判断是否过期
        RedisData data = JSONUtil.toBean(json, RedisData.class);
        JSONObject data1 = (JSONObject) data.getData();
        R r = JSONUtil.toBean(data1, type);
        //3.1过期
        if (LocalDateTime.now().isAfter(data.getExpireTime())) {
            //1)尝试拿互斥锁
            String lockKey = prefixLockKey + id;
            boolean flag = tryLock(lockKey);
            //2）拿到，开启线程进行缓存重建
            if (flag == true) {
                executor.submit(() -> {
                    try {
                        //查数据
                        //3)将数据写入redis中，并设置过期时间
                       setWithLogicExpire(key,r,time,timeUnit);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    } finally {
                        //4)释放锁
                        releaseLock(lockKey);
                    }
                });
            }
        }
        //三者都(过期重建，未过期，过期没拿到锁)
        //最终，返回旧数据
        //6.返回信息
        return r;
    }
    public <R, ID> R queryWithMutex(String prefixKey,String prefixLockKey, Class<R> type,ID id,
                                    Function<ID,R> dbFallback,Long time, TimeUnit timeUnit) {
        //1.从redis中查询商户
        String key = prefixKey + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.1命中，如果不是空值直接返回
        if (json != null && !json.equals("")) {
            return JSONUtil.toBean(json, type);
        }
        //2.2命中，是空值返回错误。
        if (json != null) {
            return null;
        }
        //3.缓存重建
        //3.1尝试获取互斥锁
        boolean flag = tryLock(prefixLockKey + id);
        try {
            if (flag == false) {
                //3.1失败,等待一段时间后再次查询。
                while (true) {
                    Thread.sleep(200);
                    json = stringRedisTemplate.opsForValue().get(key);
                    if (json != null && !json.equals("")) {
                        return JSONUtil.toBean(json, type);
                    }
                }
            }
            //3.2成功,进行重建
            //3.2.1获取锁后再次查询缓存
            //3.2.2.未命中，从数据库中查询
            R c = dbFallback.apply(id);
            //4.不存在,返回404错误信息
            if (c == null) {
                //缓存空值，避免缓存穿透
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //5.存在，写回redis中
            String jsonStr = JSONUtil.toJsonStr(c);
            stringRedisTemplate.opsForValue().set(key , jsonStr, time, timeUnit);
            return c;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //6.释放锁
            releaseLock(prefixLockKey + id);
        }
    }
    public  boolean tryLock(String k){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(k, "shopLock", LOCK_SHOP_TTL, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(flag);
    }
    public  void releaseLock(String k){
        stringRedisTemplate.delete(k);
    }
}
//    @Override
//    public Result queryWithLogicExpire(Long id) {
//        //1.从redis中查询商户
//        String shopJson = redis.opsForValue().get(CACHE_SHOP_KEY + id);
//        //2.未命中，返回错误信息
//        if(StrUtil.isBlank(shopJson)) {
//            return Result.fail("缓存未预热");
//        }
//        //3.命中，判断是否过期
//        RedisData data = JSONUtil.toBean(shopJson, RedisData.class);
//        JSONObject data1 = (JSONObject) data.getData();
//        Shop shop = JSONUtil.toBean(data1, Shop.class);
//        //3.1过期
//        if (LocalDateTime.now().isAfter(data.getExpireTime())) {
//            //1)尝试拿互斥锁
//            boolean flag = tryLock(LOCK_SHOP_KEY + id);
//            //2）拿到，开启线程进行缓存重建
//            if (flag == true) {
//                executor.submit(() -> {
//                    try {
//                        //查数据
//                        //3)将数据写入redis中，并设置过期时间
//                        dbToRedis(id, 30L);
//                    } catch (Exception e) {
//                        throw new RuntimeException(e);
//                    } finally {
//                        //4)释放锁
//                        releaseLock(CACHE_SHOP_KEY + id);
//                    }
//                });
//            }
//        }
//        //三者都(过期重建，未过期，过期没拿到锁)
//        //最终，返回旧数据
//        //6.返回信息
//        return Result.ok(shop);
//    }
//
//    @Override
//    public Result queryWithMutex(Long id) {
//        //1.从redis中查询商户
//        String shopJson = redis.opsForValue().get(CACHE_SHOP_KEY + id);
//        //2.1命中，如果不是空值直接返回
//        if(shopJson != null && !shopJson.equals("")){
//            return Result.ok(JSONUtil.toBean(shopJson,Shop.class));
//        }
//        //2.2命中，是空值返回错误。
//        if(shopJson != null){
//            return Result.fail("不存在该店铺");
//        }
//        //3.缓存重建
//        //3.1尝试获取互斥锁
//        boolean flag = tryLock(LOCK_SHOP_KEY + id);
//
//        Shop shop = null;
//        try {
//            if(flag == false){
//                //3.1失败,等待一段时间后再次查询。
//                while(true){
//                    Thread.sleep(200);
//                    shopJson = redis.opsForValue().get(CACHE_SHOP_KEY + id);
//                    if(shopJson != null && !shopJson.equals("")){
//                        return Result.ok(JSONUtil.toBean(shopJson,Shop.class));
//                    }
//                }
//            }
//            //3.2成功,进行重建
//            //3.2.1获取锁后再次查询缓存
//            //3.2.2.未命中，从数据库中查询
//            shop = query().eq("id", id).one();
//            //4.不存在,返回404错误信息
//            if(shop == null) {
//                //缓存空值，避免缓存穿透
//                redis.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
//                return Result.fail("没有对应商户");
//            }
//            //5.存在，写回redis中
//            String jsonStr = JSONUtil.toJsonStr(shop);
//            redis.opsForValue().set(CACHE_SHOP_KEY + id,jsonStr,CACHE_SHOP_TTL, TimeUnit.MINUTES);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        } finally {
//            //6.释放锁
//            releaseLock(LOCK_SHOP_KEY + id);
//        }
//
//        //7.返回信息
//        return Result.ok(shop);
//    }
//    public Result queryWithPassThrough(Long id) {
//        Shop shop = cacheRedis.queryWithPassThrough(CACHE_SHOP_KEY,Shop.class,id, this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);
//        if(shop == null){
//            return Result.fail("商品查询出错");
//        }
//        return Result.ok(shop);
//    }